package icasue.net.protocol.carfun;

import icasue.net.base.annotations.NotNull;
import icasue.net.base.annotations.Nullable;
import icasue.net.protocol.NetProtocol;
import icasue.net.protocol.carfun.commands.Command;
import icasue.net.protocol.carfun.commands.MDCommand;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

/**
 * @Author: Qiao Hang
 * @CreateDate: 2021/3/18 下午2:21
 * @UpdateDate:
 * @Description:
 */
public class CarFunProtocol implements NetProtocol {

    /**
     *
     *      ==========CarFunProtocol 定义了一种基于TCP数据传输的网络协议，该协议用于支持仙豆客户端组队模式的架构设计===========
     *
     * 请求数据包：
     *
     * @CHANNEL_CREATE\r\n
     * *CharSet: UTF-8\r\n
     * *DataType: JSON\r\n
     * *Length: 25\r\n
     * \r\n
     * {\"name\":\"casue\",\"age\":10}\r\n
     *
     * 响应数据包：
     *
     * CHANNEL_CREATE 200\r\n
     * *CharSet: UTF-8\r\n
     * *DataType: JSONAry\r\n
     * *Length: 53\r\n
     * \r\n
     * [{\"name\":\"casue\",\"age\":10},{\"name\":\"casue\",\"age\":11}]\r\n
     *
     *
     * 1.客户端请求数据包的组成：command, headers，消息体(JSON)，tail，所有基于CarFunProtocol的请求数据传输必须严格按照该格式进行数据包的组织!
     *
     *      1.1 command
     *          必须      格式：@COMMAND\r\n 用于标识请求的操作类型.
     *
     *      1.2 headers
     *          必须      格式：*CharSet: XXX\r\n            用于标识数据传输的字符集.
     *          必须      格式：*DataType: JSONAry\r\n       用于标识数据传输的数据格式, 固定位JSON / JSONAry.
     *          必须      格式：*Length: 25\r\n              用于标识本次数据传输的字节长度.
     *          非必需    格式：*Host: www.icasue.com\r\n     用于携带用户自定义的一些header
     *
     *      1.3 消息体(JSON)
     *          非必须     格式：标准的JSON / JSONAry
     *
     * 2.服务端响应数据包组成：command code，headers，消息体(JSON)，tail，所有基于CarFunProtocol的响应数据传输必须严格按照该格式进行数据包的组织!
     *      2.1 command code
     *          必须      格式 @COMMAND code\r\n    标识客户端请求的option，以及服务端处理option后的业务状态码
     *      2.2 headers
     *          必须      格式：*CharSet: XXX\r\n            用于标识数据传输的字符集.
     *          必须      格式：*DataType: JSONAry\r\n       用于标识数据传输的数据格式, 固定位JSON / JSONAry.
     *          必须      格式：*Length: 25\r\n              用于标识本次数据传输的字节长度.
     *          非必需    格式：*Host: www.icasue.com\r\n     用于携带用户自定义的一些header
     *      2.3 消息体(JSON)
     *          非必须     格式：标准的JSON / JSONAry
     */

    private static final Charset UTF_8 = Charset.forName("UTF-8");
    private static final String PROTOCOL_LINE = "\r\n";
    private static final String SPACE = " ";
    private static final String $COLON = ":";
    private static final String $AT = "@";
    private static final String $ANY = "*";

    private Charset charset = UTF_8;
    private Integer code;
    private Command command;
    private String dataType = "JSON";
    private Map<String,String> headers;
    private byte[] data;

    private CarFunProtocol(@NotNull Command command){
        this.command = command;
    }

    private CarFunProtocol(@NotNull Command command,@NotNull Integer code){
        this.command = command;
        this.code = code;
    }

    public static CarFunProtocol builder(@NotNull Command command){
        return new CarFunProtocol(command);
    }

    /*Server's response protocol build will invoking by reflect.*/
    private static CarFunProtocol builder(@NotNull Command command,@NotNull Integer code){
        return new CarFunProtocol(command,code);
    }

    public CarFunProtocol charSet(@Nullable Charset charset){
        if(charset != null){
            this.charset = charset;
        }
        return this;
    }

    public CarFunProtocol isJSONAry(boolean isAry){
        if(isAry){
            dataType = "JSONAry";
        }else {
            dataType = "JSON";
        }
        return this;
    }

    public CarFunProtocol headers(@Nullable Map<String,String> headers){
        if(headers != null){
            Map<String,String> copy = new HashMap<>(headers);
            headers.entrySet().forEach(kv -> {
                if(kv.getValue() == null)
                    copy.remove(kv.getKey());
            });
            if(!copy.isEmpty()){
                Map<String, String> getOrElse = Optional.ofNullable(this.headers).orElseGet(() -> new HashMap<>());
                getOrElse.putAll(copy);
                this.headers = getOrElse;
            }
        }
        return this;
    }

    public CarFunProtocol header(@NotNull String header,@NotNull String value){
        if(headers != null){
            this.headers.put(header,value);
        }
        return this;
    }

    public CarFunProtocol data(@Nullable String data, @Nullable Charset charset){
        if(charset != null){
            this.charset = charset;
        }
        if(data != null){
            this.data = data.getBytes(this.charset);
        }
        return this;
    }

    public CarFunProtocol data(@Nullable byte[] bytes){
        if(bytes != null && bytes.length > 0){
            this.data = bytes;
        }
        return this;
    }

    public StringBuffer build(){
        StringBuffer protocol = new StringBuffer();
        //protocol command.
        protocol.append($AT).append(command.getCommand());
        if(code != null){
            protocol.append(SPACE).append(code);
        }
        protocol.append(PROTOCOL_LINE);
        //protocol headers, most header exists as user defined.
        Map<String, String> headers = Optional.ofNullable(this.headers).orElseGet(() -> new HashMap<>());
        if(!headers.containsKey("CharSet")){
            headers.put("CharSet",charset.displayName());
        }
        if(!headers.containsKey("DataType")){
            headers.put("DataType",dataType);
        }
        if(!headers.containsKey("Length")){
            headers.put("Length",String.valueOf(data == null ? 0 : data.length));
        }
        for (Map.Entry<String, String> kv : headers.entrySet()) {
            protocol.append($ANY).append(kv.getKey()).append($COLON).append(SPACE).append(kv.getValue()).append(PROTOCOL_LINE);
        }
        //protocol header end.
        protocol.append(PROTOCOL_LINE);
        //protocol data.
        if(data != null){
            protocol.append(new String(data,charset)).append(PROTOCOL_LINE);
        }
        return protocol;
    }

    public static void main(String[] args){
        StringBuffer protocol = CarFunProtocol.builder(MDCommand.Channel.CHANNEL_CREATE)
                .headers(new HashMap<String, String>() {{
                    put("Authentication", "00012");
                    put("Token", "1xx");
                }})
                .header("Host", "localhost")
                .data("{\"name\":\"casue\",\"age\":101}", Charset.forName("UTF-8"))
                .isJSONAry(false)
                .build();
        System.out.println("---");
    }

}
